package org.internna.ossmoney.model;

import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.lang.Comparable;
import java.text.NumberFormat;
import javax.persistence.Temporal;
import javax.persistence.EnumType;
import javax.persistence.ManyToOne;
import javax.persistence.FetchType;
import javax.persistence.Enumerated;
import javax.persistence.NamedQuery;
import javax.persistence.TemporalType;
import javax.persistence.NamedQueries;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
import javax.validation.constraints.NotNull;
import org.internna.ossmoney.model.Payee;
import org.internna.ossmoney.util.DateUtils;
import org.internna.ossmoney.model.Subcategory;
import org.internna.ossmoney.model.security.UserDetails;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.roo.addon.jpa.entity.RooJpaEntity;
import org.springframework.roo.addon.serializable.RooSerializable;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;

@RooJavaBean
@RooToString
@RooJpaEntity
@RooSerializable
@RooJpaActiveRecord
@NamedQueries({
	@NamedQuery(name = "Bill.findByOwner", query = "SELECT b FROM Bill b WHERE owner = :owner ORDER BY lastTrigger"),
	@NamedQuery(name = "Bill.findPendingOrIncoming", query = "SELECT b FROM Bill b WHERE owner = :owner AND (((lastTrigger >= CURRENT_DATE) AND (lastTrigger < :triggerDate)) OR (lastTrigger <= CURRENT_DATE)) ORDER BY lastTrigger")
})
public class Bill extends AbstractEntity implements Comparable<Bill> {

    private static final long serialVersionUID = 8417911995247873460L;

    private static final String FIND_BY_OWNER = "Bill.findByOwner";

    private static final String FIND_PENDING_BILLS = "Bill.findPendingOrIncoming";

    public enum RECURRING_INTERVAL {

        WEEKLY, MONTHLY, QUARTERLY, YEARLY;

        public int days() {
        	switch (this) {
        		case WEEKLY: return 7;
        		case MONTHLY: return 30;
        		case QUARTERLY: return 90;
        		default: return 365;
        	}
        }

    }

    @NotNull
    @ManyToOne(fetch = FetchType.EAGER)
    private UserDetails owner;

    @NotNull
    @Size(min = 3)
    private String description;

    @NotNull
    @DateTimeFormat(style = "S-")
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastTrigger;

    @NotNull
    @Min(0L)
    private Double amount;

    @NotNull
    @Enumerated(EnumType.STRING)
    private RECURRING_INTERVAL period;

    @DateTimeFormat(style = "S-")
    @Temporal(TemporalType.TIMESTAMP)
    private Date lastPayment;

    @NotNull
    private Locale currency;

    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    private Payee payee;

    @NotNull
    @ManyToOne
    private Subcategory category;

    public void setPeriod(RECURRING_INTERVAL period) {
        this.period = period;
    }

    @SuppressWarnings("unchecked")
    public static List<Bill> findByOwner(UserDetails owner) {
        return entityManager().createNamedQuery(FIND_BY_OWNER).setParameter("owner", owner).getResultList();
    }

    @SuppressWarnings("unchecked")
    public static List<Bill> findPending(UserDetails owner, int days) {
        return entityManager().createNamedQuery(FIND_PENDING_BILLS).setParameter("owner", owner).setParameter("triggerDate", DateUtils.add(new Date(), days)).getResultList();
    }

    public boolean isAlreadyDue() {
        return new Date().after(lastTrigger);
    }

    public String getFormattedAmount() {
        NumberFormat formatter = NumberFormat.getCurrencyInstance(currency);
        return formatter.format(amount);
    }

    public final boolean belongsTo(UserDetails user) {
        return user != null && owner != null && user.equals(owner);
    }

    public final void updatePayment(Date date) {
    	this.lastPayment = date;
    	this.lastTrigger = DateUtils.add(lastTrigger, period.days());
    }

    @Override public final int compareTo(Bill other) {
        return other == null || other.lastTrigger == null ? -1 : lastTrigger == null ? 1 : lastTrigger.compareTo(other.lastTrigger);
    }

}
